package p2p.data
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.media.Camera;
	import flash.media.Microphone;
	import flash.net.NetGroupReplicationStrategy;
	import flash.net.NetStream;
	import flash.utils.ByteArray;
	
	import p2p.controller.events.FileSharingEvent;
	import p2p.controller.events.MessageEvent;
	import p2p.filesharing.LocalFileLoader;
	import p2p.model.vo.CallerVO;
	import p2p.model.vo.ClientVO;
	import p2p.model.vo.DescriptorVO;
	import p2p.model.vo.MessageVO;
	import p2p.model.vo.P2PSharedVO;
	import p2p.model.vo.SharingInfo;
	import p2p.utils.MessageUtils;
	
	[Event(name="messageEvent", type="p2p.controller.events.MessageEvent")]
	public class CallerData extends EventDispatcher
	{
		public static const CLIENTS_CHANGE:String = "clientsChange";
		
		private const SERVER:String = "rtmfp://p2p.rtmfp.net/";
		
		private const DEVKEY:String = "276c5fe445c6718b8a6edd72-37e9cd88c311";
		
		private var yourNameNotified:Boolean;
		
		[Bindable]
		public var caller:CallerVO;
		
		public var fileLoader:LocalFileLoader;
		
		public function CallerData()
		{
			caller = new CallerVO();
			
			fileLoader = new LocalFileLoader();
			fileLoader.addEventListener(Event.COMPLETE, fileLoaderComplete);
			
			caller.connection.addEventListener( NetStatusEvent.NET_STATUS, netStatusHandler );
		}
		
		[Bindable("clientsChange")]
		public function get clients() : Array
		{
			return caller ? caller.clients : null;
		}
		
		[Bindable("clientsChange")]
		public function get clientsCount() : uint
		{
			return clients ? clients.length : 0;
		}
		
		
		public function connect() : void
		{
			caller.connection.connect(SERVER + DEVKEY);
		}
		
		
		public function browseFileSystem() : void
		{
			fileLoader.browseFileSystem();
		}
		
		public function startReceiving(fileDescriptor:DescriptorVO) : void
		{
			trace("startReceiving");
			
			var receiveSO:P2PSharedVO = new P2PSharedVO();
			receiveSO.descriptor = fileDescriptor;
			
			caller.receivedSO.push(receiveSO);
			
			receiveObject(fileDescriptor.startChunkIndex);
		}	
		
		private function netStatusHandler(event:NetStatusEvent) : void
		{
			trace(event.info.code);
			var msgRecipTxt:String;
			switch(event.info.code)
			{
				case "NetConnection.Connect.Success":
					msgRecipTxt = MessageUtils.getWelcomeMessage(caller.selfGroupName);
					sendLocalChatMessage(msgRecipTxt, "");
					
					caller.selfKey = caller.connection.nearID;
					caller.setUpGroup();
					caller.setUpStream();
					caller.netGroup.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
					caller.outStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
					break;
				
				case "NetStream.Connect.Success":
					if (caller.outStream == event.info.stream as NetStream)
					{
						caller.publish( camera, microphone );
					}
					break;
				
				case "NetGroup.Connect.Success":
					onGroupConnected();
					break;				
				
				case "NetGroup.Neighbor.Connect":
					var client:ClientVO = caller.addClient(event.info.peerID);
					dispatchEvent( new Event(CLIENTS_CHANGE) );
					if (!yourNameNotified)
					{
						yourNameNotified = true;
						caller.sendYourName2Group();
					}
					break;
				
				case "NetGroup.Neighbor.Disconnect":
					var peerName:String = caller.getClientNameBySessionKey(event.info.peerID);
					msgRecipTxt = MessageUtils.getDisconnectionMessage(peerName);
					sendLocalChatMessage(msgRecipTxt, "");
					
					caller.removeClient(event.info.peerID);
					dispatchEvent( new Event(CLIENTS_CHANGE) );
					break;
				
				case "NetGroup.SendTo.Notify":
					event.stopImmediatePropagation();
					var msgPriv:MessageVO = MessageVO.getMessageFromObject(event.info.message);
					if(event.info.fromLocal == true)
					{
						//retrived from posting to private						
						if (msgPriv.nameNotification)
						{							
							setClientNameIfNotPresent(msgPriv.recipientMessage, msgPriv.ownerSessionKey);
						}
						else
						{
							dispatchEvent(new MessageEvent(msgPriv));
						}
					}
					else
					{
						// Forwarding
						caller.sendPrivateMessage(msgPriv);
					}
					break;
				
				case "NetGroup.Posting.Notify":
					//retrived from posting to all
					event.stopImmediatePropagation();
					var msgAll:MessageVO = MessageVO.getMessageFromObject(event.info.message);
					if (msgAll.nameNotification && msgAll.ownerSessionKey != caller.selfKey)
					{
						setClientNameIfNotPresent(msgAll.recipientMessage, msgAll.ownerSessionKey);
						//..and send back your name but privately only (now owner ID is recipient ID)
						var callbackMsg:MessageVO = new MessageVO(caller.selfName, null, msgAll.ownerSessionKey, caller.selfKey);
						callbackMsg.nameNotification = true;
						caller.sendPrivateMessage(callbackMsg);
					}
					else
					{
						dispatchEvent(new MessageEvent(msgAll));
					}
					
					switch(msgAll.type)
					{
						case MessageVO.FILE_TYPE :
							var descr:DescriptorVO = DescriptorVO.convert(msgAll.data);
							if (descr.endChunkIndex > SharingInfo.getInstance().endBusyChunkIndex)
								SharingInfo.getInstance().endBusyChunkIndex = descr.endChunkIndex;
							dispatchEvent(new FileSharingEvent(FileSharingEvent.FILE_SENT, descr));
							break;
					}
					break;
				
				//----- File sharing
				
				case "NetGroup.Replication.Fetch.SendNotify": // e.info.index
					trace("____ index: "+event.info.index);
					
					break;
				
				case "NetGroup.Replication.Fetch.Failed": // e.info.index
					trace("____ index: "+event.info.index);
					
					break;
				
				case "NetGroup.Replication.Fetch.Result": // e.info.index, e.info.object
					var evIdx:int = event.info.index;
					var p2pSO:P2PSharedVO = caller.getReceivedSOByChunkIndex(evIdx);
					var descr:DescriptorVO = p2pSO.descriptor
					
					//add new chunk to P2PSO
					p2pSO.setChunkData(evIdx, event.info.object);
					//notify NetGroup that you have received such chunk succesfully
					caller.netGroup.addHaveObjects(evIdx, evIdx);
					
					if (evIdx + 1 <= descr.endChunkIndex)
					{
						//request next chunk
						receiveObject(evIdx + 1);
						dispatchEvent(new FileSharingEvent(FileSharingEvent.FILE_RECEIVING, descr));
					}
					else
					{
						//last chunk is got
						trace("Receiving DONE");
						trace("p2pSharedObject.packetLenght: " + descr.packetLength);
						
						p2pSO.data = new ByteArray();
						for(var i:int = descr.startChunkIndex; i <= descr.endChunkIndex; i++)
						{
							p2pSO.data.writeBytes(p2pSO.chunks[i]);
						}
						
						trace("p2pSharedObject.data.bytesAvailable: "+p2pSO.data.bytesAvailable);
						trace("p2pSharedObject.data.length: "+p2pSO.data.length);
						
						dispatchEvent(new FileSharingEvent(FileSharingEvent.FILE_RECEIVED, descr));
					}					
					
					break;
				
				case "NetGroup.Replication.Request": // e.info.index, e.info.requestID
					var sentP2PSO:P2PSharedVO = caller.getSentSOByChunkIndex(event.info.index);
					caller.netGroup.writeRequestedObject(event.info.requestID, sentP2PSO.getChunkData(event.info.index));
					
					trace("____ ID: "+event.info.requestID+", index: "+event.info.index);
					break;				
			}
		}
		
		private function setClientNameIfNotPresent(name:String, clientSessionKey:String) : void
		{
			if (!getClientNameBySessionKey(clientSessionKey))
			{
				//if it is first time this client name notification, show the message that guys is connected
				var msg:String = MessageUtils.getConnectionMessage(name);
				sendLocalChatMessage(msg, "");
			}
			//if it is message about name, update related ClientVO...
			setClientNameBySessionKey(name, clientSessionKey);
		}
		
		private function sendLocalChatMessage(recipText:String, senderText:String) : void
		{
			var msgVo:MessageVO = new MessageVO(recipText, senderText, "", ""); 
			dispatchEvent(new MessageEvent(msgVo));
		}
		
		
		// file sharing procs
		
		private function fileLoaderComplete(event:Event):void
		{
			trace("fileLoaderComplete");
			startSharing(fileLoader.p2pSharedObject);
		}
		
		private function startSharing(p2pSO2Send:P2PSharedVO) : void
		{
			caller.sentSO.push(p2pSO2Send);
			trace("startSharing - chunks shared: " + p2pSO2Send.descriptor.packetLength);
			
			//send notification to all clients
			var descriptor:DescriptorVO = p2pSO2Send.descriptor;
			descriptor.senderName = caller.selfName;
			
			caller.netGroup.addHaveObjects(descriptor.startChunkIndex, descriptor.endChunkIndex);
			
			var recipientMessage:String = MessageUtils.getFileRecieveMessage(caller.selfName, descriptor.filename, descriptor.size);
			var ownerMessage:String = MessageUtils.getFileSendMessage(descriptor.filename, descriptor.size);
			
			var msg:MessageVO = new MessageVO(	recipientMessage,
												ownerMessage, 
												null, 
												caller.selfKey, 
												null, 
												MessageVO.FILE_TYPE, 
												descriptor );
			
			sendGroupMessage(msg);
			
			dispatchEvent(new MessageEvent(msg));
		}
		
		
		protected function receiveObject(index:Number):void
		{
			caller.netGroup.addWantObjects(index,index);
		}		
		
		protected function onGroupConnected():void
		{
			caller.netGroup.replicationStrategy = NetGroupReplicationStrategy.LOWEST_FIRST;
		}
		
		// ---------------------------------------------------
		// -------- Delegated methods to CallerVO ------------
		// ---------------------------------------------------
		public function setClientNameBySessionKey(name:String, sessionKey:String) : void
		{
			caller.setClientNameBySessionKey(name, sessionKey);
		}
		
		public function getClientNameBySessionKey(sessionKey:String) : String
		{
			return caller.getClientNameBySessionKey(sessionKey);
		}
		
		public function getDestinationBySessionKey(sessionKey:String) : String
		{
			return caller.getDestinationBySessionKey(sessionKey);
		}
		
		public function sendPrivateMessage(message:MessageVO) : void
		{
			caller.sendPrivateMessage(message);
		}
		
		public function sendGroupMessage(message:MessageVO) : void
		{
			caller.sendGroupMessage(message);
		}
		
		public function get camera() : Camera
		{
			return Camera.getCamera();
		}
		
		public function get microphone() : Microphone
		{
			return Microphone.getMicrophone();
		}
		
	}
}